package com.xuecheng.auth.service;

import com.alibaba.fastjson.JSONObject;
import com.xuecheng.auth.config.StringRedisTemplateEx;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.framework.exception.ExceptionCast;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class AuthService {
    @Resource //连接eureka
    private LoadBalancerClient loadBalancerClient;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private StringRedisTemplateEx stringRedisTemplateEx;

    @Value("${auth.tokenValiditySeconds}")
    private int tokenValiditySeconds;

    /**
     * 用户登录,申请令牌
     *
     * @param clientId     系统客户端id
     * @param clientSecret 系统客户端密码
     * @param username     用户名
     * @param password     用户密码
     * @return 验证成功的用户信息 {@link AuthToken}
     */
    public AuthToken userLogin(String clientId, String clientSecret, String username, String password) {
        AuthToken authToken = this.applyForJWT(clientId, clientSecret, username, password);
        if (authToken == null) {
            // 返回的authToken为null 说明没有申请到jwt,可能是密码错误或者网络错误
            ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
        }
        boolean b = this.saveToken(authToken, tokenValiditySeconds);
        if (!b) {
            ExceptionCast.cast(AuthCode.AUTH_REDIS_SAVE_TOKEN_ERROR);
        }
        return authToken;
    }

    /**
     * 将jwt存储在redis中
     *
     * @param authToken {@link AuthToken}
     * @param ttl       过期时间, 秒
     * @return 存储成功返回true, 存储失败返回false
     */
    private boolean saveToken(AuthToken authToken, int ttl) {
        String key = "user_token:" + authToken.getJti();
        String value = JSONObject.toJSONString(authToken);
        // user_token是存在0号redis库的
        StringRedisTemplate stringRedisTemplate = stringRedisTemplateEx.getTemplateByIndex(0);
        stringRedisTemplate.boundValueOps(key).set(value, ttl, TimeUnit.SECONDS);
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        return expire == null || expire >= 0;
    }

    /**
     * 远程调用自己携带的Spring Security OAuth2.0 获取JWT令牌
     *
     * @param clientId     系统客户端id
     * @param clientSecret 系统客户端密码
     * @param username     用户名
     * @param password     密码
     * @return 完整的用户登录后的信息 {@link AuthToken}
     */
    private AuthToken applyForJWT(String clientId, String clientSecret, String username, String password) {
        // 从eureka获取认证服务地址,因为认证服务被注册到了eureka
        // 获取一个eureka实例
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
        URI uri = serviceInstance.getUri();
        // 令牌申请地址 http://localhost:40400/auth/oauth/authorize?client_id=XcWebApp&response_type=code&scope=app&redirect_uri=http://localhost
        String authURL = uri + "/auth/oauth/token";
        // 定义请求header
        LinkedMultiValueMap<String, String> httpHeaders = new LinkedMultiValueMap<>();
        httpHeaders.set("Authorization", getHttpBasic(clientId, clientSecret));
        // 定义请求body
        LinkedMultiValueMap<String, String> httpBody = new LinkedMultiValueMap<>();
        httpBody.add("grant_type", "password");
        httpBody.add("username", username);
        httpBody.add("password", password);
        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<>(httpBody, httpHeaders);
        // restTemplate 会对401和400报错, 需要设置不报错,才能得到一些数据
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });
        // 远程调用自己携带的Spring Security OAuth2.0 获取JWT令牌
        try {
            ResponseEntity<Map> exchange = restTemplate.exchange(authURL, HttpMethod.POST, multiValueMapHttpEntity, Map.class);
            if (exchange.getBody().get("error_description") != null) {
                if (exchange.getBody().get("error_description").equals("坏的凭证")) {
                    return null;
                }
            }
            AuthToken body = new AuthToken();
            body.setJti(exchange.getBody().get("jti").toString());
            body.setAccess_token(exchange.getBody().get("access_token").toString());
            body.setRefresh_token(exchange.getBody().get("refresh_token").toString());
            return body;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 主要是给RestTemplate获取令牌的时候需要HttpBasic认证做的方法
     *
     * @param clientID     客户端id
     * @param clientSecret 客户端密码
     * @return 一个标准的Http Basic字符串
     */
    private String getHttpBasic(String clientID, String clientSecret) {
        byte[] encode = Base64Utils.encode((clientID + ":" + clientSecret).getBytes());
        return "Basic " + new String(encode);
    }
}
